﻿#region Copyright

// The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (the "License"); you may not use this file except in compliance
//  with the License. You may obtain a copy of the License at
//  
//  http://www.mozilla.org/MPL/
//  
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//  License for the specific language governing rights and limitations under 
//  the License.
//  
//  The Initial Developer of the Original Code is Robert Smyth.
//  Portions created by Robert Smyth are Copyright (C) 2008.
//  
//  All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using NUnitGrid.Framework.Env;
using NUnitGrid.Framework.Logging;
using NUnitGrid.Framework.Pipes;
using NUnitGrid.Node.Runner;
using NUnitGrid.Protocol;
using NUnitGrid.Protocol.Responses;


namespace NUnitGrid.Node.Plugins.ServerLink.Protocol
{
    public class RunAsProcessCommandHandler : IProtocolCommandHandler, IRunProgressListener
    {
        private readonly Stopwatch elapsedFileCopyTime = new Stopwatch();
        private readonly TimeSpan fileCopyTimeLimit = TimeSpan.FromSeconds(60);
        private readonly ILogger logger;
        private readonly IPipe pipe;
        private readonly IRunner runner;
        private int transactionID;

        public RunAsProcessCommandHandler(IPipe pipe, IRunner runner, ILogger logger)
        {
            this.pipe = pipe;
            this.runner = runner;
            this.logger = logger;
        }

        public bool CanHandle(string commandMessage)
        {
            return commandMessage.StartsWith("Command.RunOnGrid.AsProcess(");
        }

        public void Handle(string commandMessage)
        {
            logger.Info.Write("Starting run as process task.");

            var parser = new ProtocolMessageParser(commandMessage);
            transactionID = parser.GetTransactionID();
            var fileName = parser.GetValue("FileName");
            var sourceDirectory = parser.GetValue("WorkingDirectory");
            var commandLine = parser.GetValue("CommandLine");
            var timeLimit = TimeSpan.Parse(parser.GetValue("TimeLimit"));
            var sessionID = new Guid(parser.GetValue("SessionID"));

            IProtocolMessageParsing nestedPropertiesParser =
                new ProtocolMessageParser(parser.GetValue("EnvironmentVariables"));
            var enviromentVariableNames = new List<string>(nestedPropertiesParser.GetPropertyNames());

            var variables = new List<EnvironmentVariable>();
            foreach (var variableName in enviromentVariableNames)
            {
                var name = variableName.Trim();
                var value = nestedPropertiesParser.GetValue(name);
                variables.Add(new EnvironmentVariable(name, value));
            }

            var workingDirectory = GetWorkingDirectory(sourceDirectory, sessionID);

            try
            {
                runner.Run(this, workingDirectory, fileName, commandLine, timeLimit, variables.ToArray());
            }
            catch (Exception exception)
            {
                var message = string.Format("{0} exception thrown by process: '{1}'", exception, exception.Message);
                ((IRunProgressListener) this).OnCompleted(message, false, true);
                logger.Warning.Write(string.Format("Ran as process task threw exception - {0}", message));
                throw;
            }
        }

        public void CopyDirectory(string sourceDirectory, string destinationDirectory)
        {
            if (!Directory.Exists(destinationDirectory))
            {
                Directory.CreateDirectory(destinationDirectory);
            }

            var files = Directory.GetFiles(sourceDirectory);
            foreach (var file in files)
            {
                if (!file.Contains("_ReSharper"))
                {
                    while (!CopyFile(destinationDirectory, file) && elapsedFileCopyTime.Elapsed < fileCopyTimeLimit)
                    {
                        Thread.Sleep(1000);
                    }

                    if (elapsedFileCopyTime.Elapsed > fileCopyTimeLimit)
                    {
                        SendResponse(false, true,
                                     string.Format("Unable to copy files from client. Failed while copying '{0}'.",
                                                   file));
                    }
                }
            }

            var directories = Directory.GetDirectories(sourceDirectory);
            foreach (var directory in directories)
            {
                if (!directory.Contains("_ReSharper"))
                {
                    CopyDirectory(directory, Path.Combine(destinationDirectory, directory));
                }
            }
        }

        void IRunProgressListener.OnCompleted(string textOutput, bool succeeded, bool taskError)
        {
            if (!succeeded || taskError)
            {
                logger.Warning.Write("Run as process task failed.\n\n{0}", textOutput);
            }
            else
            {
                logger.Info.Write("Run as process task succeeded.");
            }

            SendResponse(succeeded, taskError, textOutput);
        }

        private string GetWorkingDirectory(string sourceDirectory, Guid sessionID)
        {
            var workingDirectory =
                Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                             @"NUnitGridRunner\Temp");
            workingDirectory = Path.Combine(workingDirectory, sessionID.ToString());
            workingDirectory = Path.Combine(workingDirectory, GridNodeSettings.Default.Nickname);

            if (!Directory.Exists(workingDirectory))
            {
                elapsedFileCopyTime.Start();
                CopyDirectory(sourceDirectory, workingDirectory);
            }

            return workingDirectory;
        }

        private bool CopyFile(string destinationDirectory, string file)
        {
            bool succeeded;
            try
            {
                File.Copy(file, Path.Combine(destinationDirectory, Path.GetFileName(file)), true);
                succeeded = true;
            }
            catch (Exception exception)
            {
                logger.Warning.Write("Exception while copying file: {0}", exception.Message);
                Thread.Sleep(3000);
                succeeded = false;
            }
            return succeeded;
        }

        private void SendResponse(bool succeeded, bool taskError, string textOutput)
        {
            var response = new ProtocolAppResponseMessage("RunOnGrid.AsProcess", transactionID);
            response.AddProperty("Succeeded", succeeded && !taskError);
            response.AddProperty("MachineName", Environment.MachineName);
            response.AddProperty("TextOutput", textOutput);
            pipe.SendMessage(response.ToString());
        }
    }
}